package com.ruoyi.common.utils;

import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Function;

/**
 * 列表属性赋值工具类
 */
public class LoadPropertyUtils {

    /**
     * 先查询主表实体，根据查询出来的关联字段，批量查询应该关联的子表实体数组，并组装到主表实体内
     *
     * @param list 主表实体数组
     * @param idProvider1 主表实体关联子表字段getter
     * @param propertyQuery 子表实体列表查询方法
     * @param idProvider2 子表实体主键getter，应该和主表getter对应相同字段
     * @param propertySetter 主表实体的子表实体setter
     */
    public static <T, PID, P> void loadProperty(
            Iterable<T> list,
            Function<T, PID> idProvider1,
            Function<Collection<PID>, Collection<P>> propertyQuery,
            Function<P, PID> idProvider2,
            BiConsumer<T, P> propertySetter) {
      /*  参数校验和初始化:
        首先检查传入的主表实体数组是否为null，如果是，则直接返回，不进行后续操作。
        创建一个空的列表 ids，用于存储所有不重复的主表关联字段值。*/
        if(list == null) {
            return;
        }
/*        收集主表关联字段值:
        遍历主表实体数组 list，对每个主表实体 t，通过 idProvider1 获取其关联子表字段的值 id。
        如果 id 不为null且在 ids 列表中不存在，则将其添加到 ids 列表中。*/
        List<PID> ids = new ArrayList<>();
        for (T t : list) {
            PID id = idProvider1.apply(t);
            if(id != null && !ids.contains(id)) {
                ids.add(id);
            }
        }
      /*  查询子表实体数组:

        如果 ids 列表为空，说明没有有效的主表关联字段值，直接返回，不进行后续查询和处理。
        调用 propertyQuery 方法，传入 ids 列表作为参数，从子表中批量查询相应的子表实体数组 properties*/
        if(ids.isEmpty()) {
            return;
        }
        Collection<P> properties = propertyQuery.apply(ids);
        if(properties.isEmpty()) {
            return;
        }
        /*
        映射子表实体:
        创建一个 map，用于将子表实体的主键与实体对象建立映射关系。
        遍历 properties 数组，对每个子表实体 p，通过 idProvider2 获取其主键值，并将 <主键, 实体> 的映射存入 map 中
         */
        Map<PID, P> map = new HashMap<>(properties.size());
        properties.forEach(p -> map.put(idProvider2.apply(p), p));
   /*     组装子表实体到主表实体:
        再次遍历主表实体数组 list，对每个主表实体 t，通过 idProvider1 获取其关联子表字段的值 pid。
        如果 pid 为null，则跳过当前循环。
        从 map 中获取与 pid 对应的子表实体 property。
        如果 property 为null，则跳过当前循环。
        使用 propertySetter 将 property 设置到主表实体 t 中。*/
        for (T t : list) {
            PID pid = idProvider1.apply(t);
            if(pid == null) {
                continue;
            }
            P property = map.get(pid);
            if(property == null) {
                continue;
            }
            propertySetter.accept(t, property);
        }
    }

    /**
     * 加载一对多关联对象，此方法一的一方会先查询出多的一方的关联id数组，用于有关联表的many to one模式
     * <p>之后通过一个查询查出关联的many放数组，重新分组给one一方的属性赋值。
     * @param list
     * @param idsProvider
     * @param propertyQuery
     * @param idProvider
     * @param propertySetter
     */
    public static <T, PID, P> void loadListProperty(Iterable<T> list, Function<T, List<PID>> idsProvider,
                                                    Function<Collection<PID>, List<P>> propertyQuery, Function<P, PID> idProvider,
                                                    BiConsumer<T, List<P>> propertySetter) {
        if(list == null) {
            return;
        }
        List<PID> ids = new ArrayList<>();
        for (T t : list) {
            List<PID> ids2 = idsProvider.apply(t);
            if(ids2 != null) {
                ids.addAll(ids2);
            }
        }
        if(ids.isEmpty()) {
            return;
        }
        List<P> properties = propertyQuery.apply(ids);
        for (T t : list) {
            List<PID> ids2 = idsProvider.apply(t);
            if(ids2 == null) {
                continue;
            }
            List<PID> ids3 = new ArrayList<>(ids2);
            List<P> pList = new ArrayList<>();
            for (P p : properties) {
                PID id = idProvider.apply(p);
                if(id != null && ids3.contains(id)) {
                    pList.add(p);
                }
            }

            propertySetter.accept(t, pList);
        }
    }

    /**
     * many to one关联方式，通过one一方的主键id，查询many一方的数组
     * <p>根据many方的foreign id重新分组，为one一方赋值。
     * @param list
     * @param idProvider
     * @param propertyQuery
     * @param foreignIdProvider
     * @param propertySetter
     */
    public static <T, PID, P> void loadManyToOneProperty(
            Iterable<T> list,
            Function<T, PID> idProvider,
            Function<Collection<PID>,
            List<P>> propertyQuery,
            Function<P, PID> foreignIdProvider,
            BiConsumer<T, List<P>> propertySetter) {
        if(list == null) {
            return;
        }
        List<PID> ids = new ArrayList<>();
        for (T t : list) {
            PID pid = idProvider.apply(t);
            if(pid != null) {
                ids.add(idProvider.apply(t));
            }
        }
        if(ids.isEmpty()) {
            return;
        }
        List<P> properties = propertyQuery.apply(ids);
        for (T t : list) {
            PID pid = idProvider.apply(t);
            if(pid == null) {
                continue;
            }
            ArrayList<P> pList = new ArrayList<>();
            for (P p : properties) {
                PID pid2 = foreignIdProvider.apply(p);
                if(pid.equals(pid2)) {
                    pList.add(p);
                }
            }
            propertySetter.accept(t, pList);
        }
    }
}
